@@ -5,8 +5,9 @@ from django.conf.urls import url  | 
            ||
| 5 | 5 | 
                from account import views as account_views  | 
            
| 6 | 6 | 
                from group import views as group_views  | 
            
| 7 | 7 | 
                from message import views as message_views  | 
            
| 8 | 
                -from photo import views as photo_views  | 
            |
| 9 | 8 | 
                from operation import views as op_views  | 
            
| 9 | 
                +from pay import views as pay_views  | 
            |
| 10 | 
                +from photo import views as photo_views  | 
            |
| 10 | 11 | 
                 | 
            
| 11 | 12 | 
                 | 
            
| 12 | 13 | 
                # 帐户相关  | 
            
                @@ -69,3 +70,9 @@ urlpatterns += [  | 
            ||
| 69 | 70 | 
                url(r'^op/upgrade$', op_views.upgrade_api, name='upgrade_api'), # APP 升级  | 
            
| 70 | 71 | 
                url(r'^op/splash$', op_views.splash_api, name='splash_api'), # 启动页面  | 
            
| 71 | 72 | 
                ]  | 
            
| 73 | 
                +  | 
            |
| 74 | 
                +# 支付相关  | 
            |
| 75 | 
                +urlpatterns += [  | 
            |
| 76 | 
                + url(r'^order/create$', pay_views.order_create_api, name='order_create_api'), # 订单创建  | 
            |
| 77 | 
                + url(r'^order/notify_url$', pay_views.notify_url_api, name='notify_url_api'), # 支付异步通知回调地址  | 
            |
| 78 | 
                +]  | 
            
                @@ -25,6 +25,9 @@ import os  | 
            ||
| 25 | 25 | 
                import shortuuid  | 
            
| 26 | 26 | 
                 | 
            
| 27 | 27 | 
                 | 
            
| 28 | 
                +r = settings.REDIS_CACHE  | 
            |
| 29 | 
                +  | 
            |
| 30 | 
                +  | 
            |
| 28 | 31 | 
                @transaction.atomic  | 
            
| 29 | 32 | 
                def group_create_api(request):  | 
            
| 30 | 33 | 
                     user_id = request.POST.get('user_id', '')
               | 
            
                @@ -0,0 +1,11 @@  | 
            ||
| 1 | 
                +# -*- coding: utf-8 -*-  | 
            |
| 2 | 
                +  | 
            |
| 3 | 
                +import redis  | 
            |
| 4 | 
                +  | 
            |
| 5 | 
                +  | 
            |
| 6 | 
                +def redis_connect(settings):  | 
            |
| 7 | 
                +    return redis.StrictRedis(**{
               | 
            |
| 8 | 
                +        'host': settings.get('HOST', ''),
               | 
            |
| 9 | 
                +        'port': settings.get('PORT', 0),
               | 
            |
| 10 | 
                +        'password': '{user}:{pwd}'.format(settings.get('USER', ''), settings.get('PASSWORD', '')) if settings.get('USER', '') else ''
               | 
            |
| 11 | 
                + })  | 
            
                @@ -46,6 +46,7 @@ INSTALLED_APPS = (  | 
            ||
| 46 | 46 | 
                'group',  | 
            
| 47 | 47 | 
                'message',  | 
            
| 48 | 48 | 
                'operation',  | 
            
| 49 | 
                + 'pay',  | 
            |
| 49 | 50 | 
                'photo',  | 
            
| 50 | 51 | 
                )  | 
            
| 51 | 52 | 
                 | 
            
                @@ -158,6 +159,41 @@ REST_FRAMEWORK = {
               | 
            ||
| 158 | 159 | 
                'PAGE_SIZE': 1  | 
            
| 159 | 160 | 
                }  | 
            
| 160 | 161 | 
                 | 
            
| 162 | 
                +# Redis 设置  | 
            |
| 163 | 
                +REDIS = {
               | 
            |
| 164 | 
                +    'default': {
               | 
            |
| 165 | 
                + 'HOST': '127.0.0.1',  | 
            |
| 166 | 
                + 'PORT': 6379,  | 
            |
| 167 | 
                + 'USER': '',  | 
            |
| 168 | 
                + 'PASSWORD': ''  | 
            |
| 169 | 
                + }  | 
            |
| 170 | 
                +}  | 
            |
| 171 | 
                +  | 
            |
| 172 | 
                +# Redis 缓存时间设置  | 
            |
| 173 | 
                +REDIS_EXPIRED_HOUR = 3600 # 60 * 60  | 
            |
| 174 | 
                +REDIS_EXPIRED_DAY = 86400 # 24 * 60 * 60  | 
            |
| 175 | 
                +REDIS_EXPIRED_WEEK = 604800 # 7 * 24 * 60 * 60  | 
            |
| 176 | 
                +REDIS_EXPIRED_MONTH = 2678400 # 31 * 24 * 60 * 60  | 
            |
| 177 | 
                +REDIS_EXPIRED_YEAR = 31622400 # 366 * 24 * 60 * 60  | 
            |
| 178 | 
                +  | 
            |
| 179 | 
                +# 微信设置  | 
            |
| 180 | 
                +WECHAT = {
               | 
            |
| 181 | 
                + 'token': '5201314',  | 
            |
| 182 | 
                + 'appID': '',  | 
            |
| 183 | 
                + 'appsecret': '',  | 
            |
| 184 | 
                + 'mchID': '',  | 
            |
| 185 | 
                + 'apiKey': '',  | 
            |
| 186 | 
                +}  | 
            |
| 187 | 
                +  | 
            |
| 188 | 
                +WECHAT_GET_CODE = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_base&state=%s#wechat_redirect'  | 
            |
| 189 | 
                +WECHAT_GET_CODE_USERINFO = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_userinfo&state=%s#wechat_redirect'  | 
            |
| 190 | 
                +WECHAT_GET_ACCESS_TOKEN = 'https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code'  | 
            |
| 191 | 
                +  | 
            |
| 192 | 
                +WECHAT_GET_USERINFO = 'https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s'  | 
            |
| 193 | 
                +  | 
            |
| 194 | 
                +WXPAY_NOTIFY_SUCCESS = '<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>'  | 
            |
| 195 | 
                +WXPAY_NOTIFY_FAIL = '<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[XML PARSE FAIL]]></return_msg></xml>'  | 
            |
| 196 | 
                +  | 
            |
| 161 | 197 | 
                # 唯一标识设置  | 
            
| 162 | 198 | 
                CURTAIL_UUID_LENGTH = 7  | 
            
| 163 | 199 | 
                 | 
            
                @@ -168,10 +204,9 @@ WATERMARK_LOGO = os.path.join(PROJ_DIR, 'static/pai2/img/paiai_96_96.png').repla  | 
            ||
| 168 | 204 | 
                THUMBNAIL_MAX_WIDTH = 360  | 
            
| 169 | 205 | 
                 | 
            
| 170 | 206 | 
                # 域名设置  | 
            
| 171 | 
                -# DOMAIN = 'http://xfoto.com.cn'  | 
            |
| 172 | 
                -# IMG_DOMAIN = 'http://img.xfoto.com.cn'  | 
            |
| 173 | 207 | 
                DOMAIN = 'http://pai.ai'  | 
            
| 174 | 208 | 
                IMG_DOMAIN = 'http://img.pai.ai'  | 
            
| 209 | 
                +API_DOMAIN = 'http://api.pai.ai'  | 
            |
| 175 | 210 | 
                 | 
            
| 176 | 211 | 
                # 消息图片设置  | 
            
| 177 | 212 | 
                PAI2_LOGO_URL = DOMAIN + '/static/pai2/img/paiai_96_96.png'  | 
            
                @@ -186,3 +221,9 @@ try:  | 
            ||
| 186 | 221 | 
                from local_settings import *  | 
            
| 187 | 222 | 
                except ImportError:  | 
            
| 188 | 223 | 
                pass  | 
            
| 224 | 
                +  | 
            |
| 225 | 
                +try:  | 
            |
| 226 | 
                + from func_settings import redis_connect  | 
            |
| 227 | 
                +    REDIS_CACHE = redis_connect(REDIS.get('default', {}))
               | 
            |
| 228 | 
                +except ImportError:  | 
            |
| 229 | 
                + REDIS_CACHE = None  | 
            
                @@ -0,0 +1,13 @@  | 
            ||
| 1 | 
                +# -*- coding: utf-8 -*-  | 
            |
| 2 | 
                +  | 
            |
| 3 | 
                +from django.contrib import admin  | 
            |
| 4 | 
                +  | 
            |
| 5 | 
                +from pay.models import OrderInfo  | 
            |
| 6 | 
                +  | 
            |
| 7 | 
                +  | 
            |
| 8 | 
                +class OrderInfoAdmin(admin.ModelAdmin):  | 
            |
| 9 | 
                +    list_display = ('order_id', 'from_uid', 'to_lid', 'to_uid', 'pay_status', 'paid_at', 'status', 'created_at', 'updated_at')
               | 
            |
| 10 | 
                +    list_filter = ('pay_status', 'status')
               | 
            |
| 11 | 
                +  | 
            |
| 12 | 
                +  | 
            |
| 13 | 
                +admin.site.register(OrderInfo, OrderInfoAdmin)  | 
            
                @@ -0,0 +1,35 @@  | 
            ||
| 1 | 
                +# -*- coding: utf-8 -*-  | 
            |
| 2 | 
                +from __future__ import unicode_literals  | 
            |
| 3 | 
                +  | 
            |
| 4 | 
                +from django.db import models, migrations  | 
            |
| 5 | 
                +import shortuuidfield.fields  | 
            |
| 6 | 
                +  | 
            |
| 7 | 
                +  | 
            |
| 8 | 
                +class Migration(migrations.Migration):  | 
            |
| 9 | 
                +  | 
            |
| 10 | 
                + dependencies = [  | 
            |
| 11 | 
                + ]  | 
            |
| 12 | 
                +  | 
            |
| 13 | 
                + operations = [  | 
            |
| 14 | 
                + migrations.CreateModel(  | 
            |
| 15 | 
                + name='OrderInfo',  | 
            |
| 16 | 
                + fields=[  | 
            |
| 17 | 
                +                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
               | 
            |
| 18 | 
                +                ('status', models.BooleanField(default=True, help_text='\u72b6\u6001', db_index=True, verbose_name='status')),
               | 
            |
| 19 | 
                +                ('created_at', models.DateTimeField(help_text='\u521b\u5efa\u65f6\u95f4', verbose_name='created_at', auto_now_add=True)),
               | 
            |
| 20 | 
                +                ('updated_at', models.DateTimeField(help_text='\u66f4\u65b0\u65f6\u95f4', verbose_name='updated_at', auto_now=True)),
               | 
            |
| 21 | 
                +                ('order_id', shortuuidfield.fields.ShortUUIDField(help_text='\u8ba2\u5355\u552f\u4e00\u6807\u8bc6', max_length=22, editable=False, db_index=True, blank=True)),
               | 
            |
| 22 | 
                +                ('from_uid', models.CharField(help_text='\u4ed8\u6b3e\u7528\u6237\u552f\u4e00\u6807\u8bc6', max_length=255, verbose_name='from_uid', db_index=True)),
               | 
            |
| 23 | 
                +                ('to_lid', models.CharField(max_length=255, blank=True, help_text='\u6536\u6b3e\u6444\u5f71\u5e08\u552f\u4e00\u6807\u8bc6', null=True, verbose_name='to_lid', db_index=True)),
               | 
            |
| 24 | 
                +                ('to_uid', models.CharField(max_length=255, blank=True, help_text='\u6536\u6b3e\u7528\u6237\u552f\u4e00\u6807\u8bc6', null=True, verbose_name='to_uid', db_index=True)),
               | 
            |
| 25 | 
                +                ('body', models.CharField(help_text='\u5546\u54c1\u63cf\u8ff0', max_length=255, null=True, verbose_name='body', blank=True)),
               | 
            |
| 26 | 
                +                ('total_fee', models.IntegerField(default=0, help_text='\u603b\u91d1\u989d', verbose_name='total_fee')),
               | 
            |
| 27 | 
                +                ('pay_status', models.IntegerField(default=0, help_text='\u652f\u4ed8\u72b6\u6001', db_index=True, verbose_name='pay_status', choices=[(0, '\u5f85\u652f\u4ed8'), (1, '\u5df2\u652f\u4ed8')])),
               | 
            |
| 28 | 
                +                ('paid_at', models.DateTimeField(help_text='\u652f\u4ed8\u65f6\u95f4', null=True, verbose_name='paid_at', blank=True)),
               | 
            |
| 29 | 
                + ],  | 
            |
| 30 | 
                +            options={
               | 
            |
| 31 | 
                + 'verbose_name': 'orderinfo',  | 
            |
| 32 | 
                + 'verbose_name_plural': 'orderinfo',  | 
            |
| 33 | 
                + },  | 
            |
| 34 | 
                + ),  | 
            |
| 35 | 
                + ]  | 
            
                @@ -0,0 +1,52 @@  | 
            ||
| 1 | 
                +# -*- coding: utf-8 -*-  | 
            |
| 2 | 
                +  | 
            |
| 3 | 
                +from django.conf import settings  | 
            |
| 4 | 
                +from django.db import models  | 
            |
| 5 | 
                +from django.utils.translation import ugettext_lazy as _  | 
            |
| 6 | 
                +  | 
            |
| 7 | 
                +from shortuuidfield import ShortUUIDField  | 
            |
| 8 | 
                +  | 
            |
| 9 | 
                +from pai2.basemodels import CreateUpdateMixin  | 
            |
| 10 | 
                +  | 
            |
| 11 | 
                +  | 
            |
| 12 | 
                +class OrderInfo(CreateUpdateMixin):  | 
            |
| 13 | 
                + WAITING_PAY = 0  | 
            |
| 14 | 
                + PAID = 1  | 
            |
| 15 | 
                + # DELETED = 2  | 
            |
| 16 | 
                +  | 
            |
| 17 | 
                + PAY_STATUS = (  | 
            |
| 18 | 
                + (WAITING_PAY, u'待支付'),  | 
            |
| 19 | 
                + (PAID, u'已支付'),  | 
            |
| 20 | 
                + # (DELETED, u'已删除'),  | 
            |
| 21 | 
                + )  | 
            |
| 22 | 
                +  | 
            |
| 23 | 
                + order_id = ShortUUIDField(_(u'order_id'), max_length=255, help_text=u'订单唯一标识', db_index=True)  | 
            |
| 24 | 
                +  | 
            |
| 25 | 
                + from_uid = models.CharField(_(u'from_uid'), max_length=255, help_text=u'付款用户唯一标识', db_index=True)  | 
            |
| 26 | 
                + to_lid = models.CharField(_(u'to_lid'), max_length=255, blank=True, null=True, help_text=u'收款摄影师唯一标识', db_index=True)  | 
            |
| 27 | 
                + to_uid = models.CharField(_(u'to_uid'), max_length=255, blank=True, null=True, help_text=u'收款用户唯一标识', db_index=True)  | 
            |
| 28 | 
                +  | 
            |
| 29 | 
                + body = models.CharField(_(u'body'), max_length=255, blank=True, null=True, help_text=u'商品描述')  | 
            |
| 30 | 
                + total_fee = models.IntegerField(_(u'total_fee'), default=0, help_text=u'总金额')  | 
            |
| 31 | 
                +  | 
            |
| 32 | 
                + pay_status = models.IntegerField(_(u'pay_status'), choices=PAY_STATUS, default=WAITING_PAY, help_text=u'支付状态', db_index=True)  | 
            |
| 33 | 
                + paid_at = models.DateTimeField(_(u'paid_at'), blank=True, null=True, help_text=_(u'支付时间'))  | 
            |
| 34 | 
                +  | 
            |
| 35 | 
                + class Meta:  | 
            |
| 36 | 
                +        verbose_name = _('orderinfo')
               | 
            |
| 37 | 
                +        verbose_name_plural = _('orderinfo')
               | 
            |
| 38 | 
                +  | 
            |
| 39 | 
                + def __unicode__(self):  | 
            |
| 40 | 
                +        return u'{0.pk}'.format(self)
               | 
            |
| 41 | 
                +  | 
            |
| 42 | 
                + @property  | 
            |
| 43 | 
                + def data(self):  | 
            |
| 44 | 
                +        return {
               | 
            |
| 45 | 
                + 'order_id': self.order_id,  | 
            |
| 46 | 
                + 'from_uid': self.from_uid,  | 
            |
| 47 | 
                + 'to_lid': self.to_lid,  | 
            |
| 48 | 
                + 'to_uid': self.to_uid,  | 
            |
| 49 | 
                + 'pay_status': self.pay_status,  | 
            |
| 50 | 
                + 'paid_at': self.paid_at,  | 
            |
| 51 | 
                + 'created_at': self.created_at,  | 
            |
| 52 | 
                + }  | 
            
                @@ -0,0 +1,3 @@  | 
            ||
| 1 | 
                +from django.test import TestCase  | 
            |
| 2 | 
                +  | 
            |
| 3 | 
                +# Create your tests here.  | 
            
                @@ -0,0 +1,94 @@  | 
            ||
| 1 | 
                +# -*- coding: utf-8 -*-  | 
            |
| 2 | 
                +  | 
            |
| 3 | 
                +from django.conf import settings  | 
            |
| 4 | 
                +from django.db import transaction  | 
            |
| 5 | 
                +from django.http import JsonResponse  | 
            |
| 6 | 
                +from django.shortcuts import HttpResponse  | 
            |
| 7 | 
                +  | 
            |
| 8 | 
                +from pay.models import OrderInfo  | 
            |
| 9 | 
                +  | 
            |
| 10 | 
                +from utils.errno_utils import OrderStatusCode  | 
            |
| 11 | 
                +from utils.response_utils import response  | 
            |
| 12 | 
                +  | 
            |
| 13 | 
                +from TimeConvert import TimeConvert as tc  | 
            |
| 14 | 
                +from wechatpy import WeChatPay, WeChatPayException  | 
            |
| 15 | 
                +  | 
            |
| 16 | 
                +import xmltodict  | 
            |
| 17 | 
                +  | 
            |
| 18 | 
                +WECHAT = settings.WECHAT  | 
            |
| 19 | 
                +  | 
            |
| 20 | 
                +wxpay = WeChatPay(WECHAT['appID'], WECHAT['apiKey'], WECHAT['mchID'])  | 
            |
| 21 | 
                +  | 
            |
| 22 | 
                +  | 
            |
| 23 | 
                +@transaction.atomic  | 
            |
| 24 | 
                +def order_create_api(request):  | 
            |
| 25 | 
                +    from_uid = request.POST.get('from_uid', '')
               | 
            |
| 26 | 
                +    to_lid = request.POST.get('to_lid', '')
               | 
            |
| 27 | 
                +    to_uid = request.POST.get('to_uid', '')
               | 
            |
| 28 | 
                +  | 
            |
| 29 | 
                +    body = request.POST.get('body', '')  # 商品描述
               | 
            |
| 30 | 
                +    total_fee = int(request.POST.get('total_fee', 0))  # 总金额,单位分
               | 
            |
| 31 | 
                +  | 
            |
| 32 | 
                + # JSAPI--公众号支付、NATIVE--原生扫码支付、APP--app支付,统一下单接口trade_type的传参可参考这里  | 
            |
| 33 | 
                +    trade_type = request.POST.get('trade_type', '')
               | 
            |
| 34 | 
                +  | 
            |
| 35 | 
                + # 生成订单  | 
            |
| 36 | 
                + order = OrderInfo.objects.create(from_uid=from_uid, to_lid=to_lid, to_uid=to_uid, total_fee=total_fee)  | 
            |
| 37 | 
                +  | 
            |
| 38 | 
                + try:  | 
            |
| 39 | 
                + prepay_data = wxpay.order.create(  | 
            |
| 40 | 
                + body=body,  | 
            |
| 41 | 
                + notify_url=settings.API_DOMAIN + '/order/notify_url',  | 
            |
| 42 | 
                + out_trade_no=order.order_id,  | 
            |
| 43 | 
                + total_fee=total_fee,  | 
            |
| 44 | 
                + trade_type=trade_type,  | 
            |
| 45 | 
                + # user_id=None, # 可选,用户在商户appid下的唯一标识。trade_type=JSAPI,此参数必传  | 
            |
| 46 | 
                + )  | 
            |
| 47 | 
                + except WeChatPayException:  | 
            |
| 48 | 
                + return response(OrderStatusCode.WX_UNIFIED_ORDER_FAIL)  | 
            |
| 49 | 
                +  | 
            |
| 50 | 
                +    prepay_id = prepay_data.get('prepay_id', '')
               | 
            |
| 51 | 
                + wxpay_params = wxpay.jsapi.get_jsapi_params(prepay_id)  | 
            |
| 52 | 
                +  | 
            |
| 53 | 
                +    return JsonResponse({
               | 
            |
| 54 | 
                + 'status': 200,  | 
            |
| 55 | 
                +        'data': {
               | 
            |
| 56 | 
                + 'order_id': order.order_id,  | 
            |
| 57 | 
                + 'prepay_id': prepay_id,  | 
            |
| 58 | 
                + 'wxpay_params': wxpay_params,  | 
            |
| 59 | 
                + }  | 
            |
| 60 | 
                + })  | 
            |
| 61 | 
                +  | 
            |
| 62 | 
                +  | 
            |
| 63 | 
                +def order_paid_success(order):  | 
            |
| 64 | 
                + if order.pay_status == OrderInfo.PAID:  | 
            |
| 65 | 
                + return  | 
            |
| 66 | 
                +  | 
            |
| 67 | 
                + order.pay_status = OrderInfo.PAID  | 
            |
| 68 | 
                + order.paid_at = tc.utc_datetime()  | 
            |
| 69 | 
                + order.save()  | 
            |
| 70 | 
                +  | 
            |
| 71 | 
                +  | 
            |
| 72 | 
                +@transaction.atomic  | 
            |
| 73 | 
                +def notify_url_api(request):  | 
            |
| 74 | 
                + try:  | 
            |
| 75 | 
                + data = xmltodict.parse(request.body)['xml']  | 
            |
| 76 | 
                + except xmltodict.ParsingInterrupted:  | 
            |
| 77 | 
                + # 解析 XML 失败  | 
            |
| 78 | 
                + return HttpResponse(settings.WXPAY_NOTIFY_FAIL)  | 
            |
| 79 | 
                +  | 
            |
| 80 | 
                +    out_trade_no = data.get('out_trade_no', '')
               | 
            |
| 81 | 
                +    return_code = data.get('return_code', '')
               | 
            |
| 82 | 
                +    result_code = data.get('result_code', '')
               | 
            |
| 83 | 
                +  | 
            |
| 84 | 
                + if return_code != 'SUCCESS' or result_code != 'SUCCESS':  | 
            |
| 85 | 
                + return HttpResponse(settings.WXPAY_NOTIFY_FAIL)  | 
            |
| 86 | 
                +  | 
            |
| 87 | 
                + try:  | 
            |
| 88 | 
                + order = OrderInfo.objects.get(order=out_trade_no)  | 
            |
| 89 | 
                + except OrderInfo.DoesNotExist:  | 
            |
| 90 | 
                + return HttpResponse(settings.WXPAY_NOTIFY_FAIL)  | 
            |
| 91 | 
                +  | 
            |
| 92 | 
                + order_paid_success(order)  | 
            |
| 93 | 
                +  | 
            |
| 94 | 
                + return HttpResponse(settings.WXPAY_NOTIFY_SUCCESS)  | 
            
                @@ -63,7 +63,8 @@ class PhotosInfo(CreateUpdateMixin):  | 
            ||
| 63 | 63 | 
                def r_photo_url(self):  | 
            
| 64 | 64 | 
                         return u'{0}/{1}'.format(settings.IMG_DOMAIN, self.r_photo_path) if self.r_photo_path else ''
               | 
            
| 65 | 65 | 
                 | 
            
| 66 | 
                - def _data(self):  | 
            |
| 66 | 
                + @property  | 
            |
| 67 | 
                + def data(self):  | 
            |
| 67 | 68 | 
                         return {
               | 
            
| 68 | 69 | 
                'pk': self.pk,  | 
            
| 69 | 70 | 
                'user': self.lensman_id,  | 
            
                @@ -71,7 +72,8 @@ class PhotosInfo(CreateUpdateMixin):  | 
            ||
| 71 | 72 | 
                'photo': self.photo_id,  | 
            
| 72 | 73 | 
                }  | 
            
| 73 | 74 | 
                 | 
            
| 74 | 
                - def _detail(self):  | 
            |
| 75 | 
                + @property  | 
            |
| 76 | 
                + def detail(self):  | 
            |
| 75 | 77 | 
                         return {
               | 
            
| 76 | 78 | 
                'pk': self.pk,  | 
            
| 77 | 79 | 
                'user': self.lensman_id,  | 
            
                @@ -79,6 +81,3 @@ class PhotosInfo(CreateUpdateMixin):  | 
            ||
| 79 | 81 | 
                'photo': self.photo_id,  | 
            
| 80 | 82 | 
                'photo_url': self.p_photo_url,  | 
            
| 81 | 83 | 
                }  | 
            
| 82 | 
                -  | 
            |
| 83 | 
                - data = property(_data)  | 
            |
| 84 | 
                - detail = property(_detail)  | 
            
                @@ -2,6 +2,7 @@ CodeConvert==2.0.4  | 
            ||
| 2 | 2 | 
                Django==1.8.4  | 
            
| 3 | 3 | 
                MySQL-python==1.2.5  | 
            
| 4 | 4 | 
                TimeConvert==1.1.6  | 
            
| 5 | 
                +cryptography==1.2.1  | 
            |
| 5 | 6 | 
                django-curtail-uuid==1.0.0  | 
            
| 6 | 7 | 
                django-multidomain==1.1.4  | 
            
| 7 | 8 | 
                django-shortuuidfield==0.1.3  | 
            
                @@ -12,5 +13,7 @@ kkconst==1.1.2  | 
            ||
| 12 | 13 | 
                pep8==1.6.2  | 
            
| 13 | 14 | 
                pillow==2.9.0  | 
            
| 14 | 15 | 
                pytz==2015.7  | 
            
| 16 | 
                +redis==2.10.5  | 
            |
| 15 | 17 | 
                shortuuid==0.4.2  | 
            
| 16 | 18 | 
                uWSGI==2.0.11.1  | 
            
| 19 | 
                +wechatpy==1.2.5  | 
            
                @@ -15,6 +15,7 @@ class StatusCodeField(ConstIntField):  | 
            ||
| 15 | 15 | 
                 | 
            
| 16 | 16 | 
                 | 
            
| 17 | 17 | 
                class UserStatusCode(BaseStatusCode):  | 
            
| 18 | 
                + """ 摄影师/用户相关错误码 400x & 401x """  | 
            |
| 18 | 19 | 
                LENSMAN_NOT_FOUND = StatusCodeField(4000, u'Lensman Not Found', description=u'摄影师不存在')  | 
            
| 19 | 20 | 
                LENSMAN_PASSWORD_ERROR = StatusCodeField(4001, u'Lensman Password Error', description=u'摄影师密码错误')  | 
            
| 20 | 21 | 
                USERNAME_HAS_REGISTERED = StatusCodeField(4010, u'Username Has Registered', description=u'用户名已注册')  | 
            
                @@ -23,27 +24,36 @@ class UserStatusCode(BaseStatusCode):  | 
            ||
| 23 | 24 | 
                 | 
            
| 24 | 25 | 
                 | 
            
| 25 | 26 | 
                class PhotoStatusCode(BaseStatusCode):  | 
            
| 27 | 
                + """ 照片相关错误码 403x """  | 
            |
| 26 | 28 | 
                PARAMS_ERROR = StatusCodeField(4039, u'Params Error', description=u'参数错误')  | 
            
| 27 | 29 | 
                 | 
            
| 28 | 30 | 
                 | 
            
| 29 | 31 | 
                class GroupStatusCode(BaseStatusCode):  | 
            
| 32 | 
                + """ 群组相关错误码 402x """  | 
            |
| 30 | 33 | 
                GROUP_NOT_FOUND = StatusCodeField(4020, u'Group Not Found', description=u'群组不存在')  | 
            
| 31 | 34 | 
                GROUP_HAS_LOCKED = StatusCodeField(4021, u'Group Has Locked', description=u'群组已锁定')  | 
            
| 32 | 35 | 
                NOT_GROUP_ADMIN = StatusCodeField(4022, u'Not Group Admin', description=u'非群组管理员')  | 
            
| 33 | 36 | 
                NO_UPDATE_PERMISSION = StatusCodeField(40220, u'No Update Permission', description=u'没有更新权限')  | 
            
| 34 | 37 | 
                NO_LOCK_PERMISSION = StatusCodeField(40221, u'No Lock Permission', description=u'没有锁定权限')  | 
            
| 35 | 
                - NO_UNLOCK_PERMISSION = StatusCodeField(40221, u'No Unlock Permission', description=u'没有解锁权限')  | 
            |
| 36 | 
                - NO_REMOVE_PERMISSION = StatusCodeField(40222, u'No Remove Permission', description=u'没有移除权限')  | 
            |
| 37 | 
                - NO_PASS_PERMISSION = StatusCodeField(40223, u'No Pass Permission', description=u'没有通过权限')  | 
            |
| 38 | 
                - NO_REFUSE_PERMISSION = StatusCodeField(40224, u'No Refuse Permission', description=u'没有拒绝权限')  | 
            |
| 38 | 
                + NO_UNLOCK_PERMISSION = StatusCodeField(40222, u'No Unlock Permission', description=u'没有解锁权限')  | 
            |
| 39 | 
                + NO_REMOVE_PERMISSION = StatusCodeField(40223, u'No Remove Permission', description=u'没有移除权限')  | 
            |
| 40 | 
                + NO_PASS_PERMISSION = StatusCodeField(40224, u'No Pass Permission', description=u'没有通过权限')  | 
            |
| 41 | 
                + NO_REFUSE_PERMISSION = StatusCodeField(40225, u'No Refuse Permission', description=u'没有拒绝权限')  | 
            |
| 39 | 42 | 
                DUPLICATE_JOIN_REQUEST = StatusCodeField(4027, u'Duplicate Join Request', description=u'重复加群申请')  | 
            
| 40 | 43 | 
                JOIN_REQUEST_NOT_FOUND = StatusCodeField(4028, u'Join Request Not Found', description=u'加群申请不存在')  | 
            
| 41 | 44 | 
                GROUP_USER_NOT_FOUND = StatusCodeField(4029, u'Group User Not Found', description=u'该用户不在群组')  | 
            
| 42 | 45 | 
                 | 
            
| 43 | 46 | 
                 | 
            
| 44 | 47 | 
                class GroupPhotoStatusCode(BaseStatusCode):  | 
            
| 48 | 
                + """ 飞图相关错误码 403x """  | 
            |
| 45 | 49 | 
                GROUP_PHOTO_NOT_FOUND = StatusCodeField(4030, u'Group Photo Not Found', description=u'飞图不存在')  | 
            
| 46 | 50 | 
                 | 
            
| 47 | 51 | 
                 | 
            
| 52 | 
                +class OrderStatusCode(BaseStatusCode):  | 
            |
| 53 | 
                + """ 订单/支付相关错误码 404x """  | 
            |
| 54 | 
                + WX_UNIFIED_ORDER_FAIL = StatusCodeField(4040, u'WX Unified Order Fail', description=u'微信统一下单失败')  | 
            |
| 55 | 
                +  | 
            |
| 56 | 
                +  | 
            |
| 48 | 57 | 
                class MessageStatusCode(BaseStatusCode):  | 
            
| 58 | 
                + """ 消息相关错误码 409x """  | 
            |
| 49 | 59 | 
                MESSAGE_NOT_FOUND = StatusCodeField(4091, u'Message Not Found', description=u'消息不存在')  |